// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package cmd

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/spf13/cobra"

	"github.com/elastic/elastic-package/internal/builder"
	"github.com/elastic/elastic-package/internal/cobraext"
	"github.com/elastic/elastic-package/internal/reportgenerator"
	_ "github.com/elastic/elastic-package/internal/reportgenerator/generators" // register all report generators
	"github.com/elastic/elastic-package/internal/reportgenerator/outputs"
)

const (
	benchmarksFolder      = "benchmark-report"
	reportLongDescription = `Use this command to generate various reports relative to the packages. Currently, the following types of reports are available:

#### Benchmark report for Github

These report will be generated by comparing local benchmark results against ones from another benchmark run.
The report will show performance differences between both runs.

It is formatted as a Markdown Github comment to use as part of the CI results.
`
)

func setupReportsCommand() *cobraext.Command {
	var reportTypeCmdActions []cobraext.CommandAction

	cmd := &cobra.Command{
		Use:   "report",
		Short: "Generate reports",
		Long:  reportLongDescription,
		RunE: func(cmd *cobra.Command, args []string) error {
			cmd.Println("Generate reports")

			if len(args) > 0 {
				return fmt.Errorf("unsupported report type: %s", args[0])
			}

			return cobraext.ComposeCommandActions(cmd, args, reportTypeCmdActions...)
		},
	}

	cmd.PersistentFlags().BoolP(cobraext.FailOnMissingFlagName, "m", false, cobraext.FailOnMissingFlagDescription)
	cmd.PersistentFlags().BoolP(cobraext.ReportFullFlagName, "", false, cobraext.ReportFullFlagDescription)
	cmd.PersistentFlags().StringP(cobraext.ReportOutputFlagName, "", string(outputs.OutputFile), cobraext.ReportOutputFlagDescription)
	cmd.PersistentFlags().StringP(cobraext.ReportOutputPathFlagName, "", "", fmt.Sprintf(cobraext.ReportOutputPathFlagDescription, benchmarksFolder))

	// add benchmark report creation subcommand
	cmd.AddCommand(getBenchReportCommand())

	return cobraext.NewCommand(cmd, cobraext.ContextPackage)
}

func getBenchReportCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "benchmark",
		Short: "Generate a benchmark report",
		Long:  "Generate a benchmark report comparing local results against ones from another benchmark run.",
		Args:  cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			cmd.Printf("Generate benchmark reports\n")

			failOnMissing, err := cmd.Flags().GetBool(cobraext.FailOnMissingFlagName)
			if err != nil {
				return cobraext.FlagParsingError(err, cobraext.FailOnMissingFlagName)
			}

			reportOutput, err := cmd.Flags().GetString(cobraext.ReportOutputFlagName)
			if err != nil {
				return cobraext.FlagParsingError(err, cobraext.ReportOutputFlagName)
			}

			reportOutputPath, err := cmd.Flags().GetString(cobraext.ReportOutputPathFlagName)
			if err != nil {
				return cobraext.FlagParsingError(err, cobraext.ReportOutputPathFlagName)
			}
			if reportOutputPath == "" {
				dest, err := resultsDir()
				if err != nil {
					return fmt.Errorf("could not determine benchmark reports folder: %w", err)
				}
				reportOutputPath = dest
			}

			isFull, err := cmd.Flags().GetBool(cobraext.ReportFullFlagName)
			if err != nil {
				return cobraext.FlagParsingError(err, cobraext.ReportFullFlagName)
			}

			newPath, err := cmd.Flags().GetString(cobraext.BenchReportNewPathFlagName)
			if err != nil {
				return cobraext.FlagParsingError(err, cobraext.BenchReportNewPathFlagName)
			}

			oldPath, err := cmd.Flags().GetString(cobraext.BenchReportOldPathFlagName)
			if err != nil {
				return cobraext.FlagParsingError(err, cobraext.BenchReportOldPathFlagName)
			}

			threshold, err := cmd.Flags().GetFloat64(cobraext.BenchThresholdFlagName)
			if err != nil {
				return cobraext.FlagParsingError(err, cobraext.BenchThresholdFlagName)
			}

			if _, err := os.Stat(newPath); err != nil {
				err = fmt.Errorf("stat file failed for the new path (path: %s): %w", newPath, err)
				if failOnMissing {
					return err
				}
				cmd.Println(err)
				return nil
			}

			if _, err := os.Stat(oldPath); err != nil {
				err = fmt.Errorf("stat file failed for the old path (path: %s): %w", oldPath, err)
				if failOnMissing {
					return err
				}
				cmd.Println(err)
				return nil
			}

			report, err := reportgenerator.Generate("benchmark", reportgenerator.ReportOptions{
				NewPath:   newPath,
				OldPath:   oldPath,
				Threshold: threshold,
				Full:      isFull,
			})
			if err != nil {
				return err
			}

			if err := reportgenerator.WriteReport(
				reportgenerator.ReportOutput(reportOutput),
				report,
				(reportgenerator.ReportGenerators()["benchmark"]).Format(),
				reportOutputPath,
			); err != nil {
				return fmt.Errorf("error writing benchmark report: %w", err)
			}

			return err
		},
	}

	cmd.Flags().StringP(cobraext.BenchReportNewPathFlagName, "", "", cobraext.BenchReportNewPathFlagDescription)
	cmd.Flags().StringP(cobraext.BenchReportOldPathFlagName, "", "", cobraext.BenchReportOldPathFlagDescription)
	cmd.Flags().Float64P(cobraext.BenchThresholdFlagName, "", 10, cobraext.BenchThresholdFlagDescription)

	return cmd
}

// resultsDir returns the location of the directory to store reports.
func resultsDir() (string, error) {
	buildDir, err := builder.BuildDirectory()
	if err != nil {
		return "", fmt.Errorf("locating build directory failed: %w", err)
	}
	return filepath.Join(buildDir, benchmarksFolder), nil
}
